// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "VmiTcpSocket.h"
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log/logging.h"
#if ENABLE_STATISTICS
#include "Statistics/Statistics.h"
#include "Statistics/StatisticsObject.h"
#endif

using namespace std;

namespace {
    const long SEND_TIMEOUT = 10;
}
/*lint -save -e1401 */
/**
 * @功能描述：带参构造函数
 * @参数 [in] fd：套接字句柄
 * @参数 [in] address：IP地址、端口
 * @参数 [in] socketOption：套接字选项
 */
VmiTcpSocket::VmiTcpSocket(int fd, const VmiSocketAddressGroup &address, unsigned int socketOption)
    : VmiSocket(fd, address, socketOption)
{
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
        INFO("Unable to catch signal SIGPIPE");
    }
}
/*lint -restore */
/*
 * @功能描述：虚析构函数，关闭socket
 */
VmiTcpSocket::~VmiTcpSocket()
{
    CloseSocket();
}

/*
 * @功能描述：连接服务端
 * @返回值：未实现，直接返回0
 */
int VmiTcpSocket::Connect()
{
    return 0;
}

/*
 * @功能描述：设置发送超时时间
 * @参数 [in] sec：秒数部分
 * @参数 [in] usec：微秒数部分
 */
void VmiTcpSocket::SetSendTimeout(long sec, long usec)
{
    struct timeval timeout = {
        .tv_sec = sec,
        .tv_usec = usec,
    };

    if (setsockopt(m_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) {
        ERR("setsockopt SO_SNDTIMEO failed errno: (%d): %s", errno, strerror(errno));
    }
}

/*
 * @功能描述：设置接收超时时间
 * @参数 [in] sec：秒数部分
 * @参数 [in] usec：微秒数部分
 */
void VmiTcpSocket::SetRecvTimeout(long sec, long usec)
{
    struct timeval rcvTimeout = {
        .tv_sec = sec,
        .tv_usec = usec,
    };

    if (setsockopt(m_fd, SOL_SOCKET, SO_RCVTIMEO, &rcvTimeout, sizeof(rcvTimeout)) < 0) {
        ERR("setsockopt SO_RCVTIMEO failed errno: (%d): %s", errno, strerror(errno));
    }
}

/*
 * @功能描述：更新已发送字节数统计
 * @参数 [in] bytes：新增发送的字节数
 */
void VmiTcpSocket::UpdateBytesSent(size_t bytes)
{
#if ENABLE_STATISTICS
    Statistics *statistics = Statistics::GetInstance();
    if (statistics != nullptr) {
        static StatisticsObject *netStatisticsObject = statistics->AllocStatisticsObject(NET_TABLE_NAME,
            NET_TABLE_COLUMNS, SAMPLE_INTERVAL_PERSECOND);

        if (netStatisticsObject != nullptr) {
            static int indexSendByte = netStatisticsObject->GetComponentIndex(NET_COLUMN_SEND_BYTES);
            netStatisticsObject->UpdateComponent(indexSendByte, TOTAL, static_cast<int64_t>(bytes));
        }
    }
#else
    (void)bytes;
#endif
}

/*
 * @功能描述：更新已接收字节数统计
 * @参数 [in] bytes：新增接收的字节数
 */
void VmiTcpSocket::UpdateBytesRecv(size_t bytes)
{
#if ENABLE_STATISTICS
    Statistics *statistics = Statistics::GetInstance();
    if (statistics != nullptr) {
        static StatisticsObject *netStatisticsObject = statistics->AllocStatisticsObject(NET_TABLE_NAME,
            NET_TABLE_COLUMNS, SAMPLE_INTERVAL_PERSECOND);

        if (netStatisticsObject != nullptr) {
            static int indexRecvByte = netStatisticsObject->GetComponentIndex(NET_COLUMN_RECV_BYTES);
            netStatisticsObject->UpdateComponent(indexRecvByte, TOTAL, static_cast<int64_t>(bytes));
        }
    }
#else
        (void)bytes;
#endif
}

/*
 * @功能描述：尽可能发送全部数据
 * @参数 [in] sendBuf：待发送数据
 * @返回值：大于0表示实际发送的字节数，小于等于0表示失败
 */
ssize_t VmiTcpSocket::SendFully(const VmiBuffer &sendBuf)
{
    uint8_t *buf = sendBuf.GetPointer();
    size_t size = sendBuf.GetSize();
    ssize_t errCode = 0;
    size_t remain = size;

    struct timeval curTime = {};
    struct timeval startTime = {};
    (void)gettimeofday(&startTime, nullptr);

    lock_guard<mutex> lock(m_socketMutex);
    while (1) {
        ssize_t ret = ::send(m_fd, buf, remain, MSG_NOSIGNAL);
        if (ret >= 0) {
            size_t bytesSent = static_cast<size_t>(ret);
            if (bytesSent == remain) {
                UpdateBytesSent(bytesSent);
                return size;
            } else if (bytesSent < remain) {
                remain -= bytesSent;
                UpdateBytesSent(bytesSent);
                buf = buf + bytesSent;

                (void)gettimeofday(&curTime, nullptr);
                if ((curTime.tv_sec - startTime.tv_sec) > SEND_TIMEOUT) { // 发送超时返回断开
                    errCode = SOCKET_SEND_FAIL_DISCONN;
                    ERR("Send uncompleted, less byte:%zu", remain);
                    break;
                }

                continue;
            }
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR || errno == ETIMEDOUT) {
                ERR("TCP try again, errno:%d, ret:%zd", errno, ret);
                (void)gettimeofday(&curTime, nullptr);
                if ((curTime.tv_sec - startTime.tv_sec) > SEND_TIMEOUT) { // 发送超时返回断开
                    errCode = SOCKET_SEND_FAIL_DISCONN;
                    ERR("TCP Send break errno:%zd, ret:%zd", errno, ret);
                    break;
                }

                continue;
            } else {
                ERR("TCP Send errno:%d, ret:%zd", errno, ret);
                errCode = SOCKET_SEND_FAIL_DISCONN;
                break;
            }
        }
    }
    return errCode;
}

/*
 * @功能描述：通过TCP发送数据
 * @参数 [in] sendBuf：待发送数据
 * @返回值：大于等于0表示实际发送的字节数，小于0表示失败
 */
ssize_t VmiTcpSocket::Send(const VmiBuffer &sendBuf)
{
    if (GetStatus() != SOCKET_STATUS_RUNNING) {
        DBG("TCP socket disconnected, send fail");
        return SOCKET_SEND_FAIL_DISCONN;
    }

    // 检查入参
    uint8_t *buf = sendBuf.GetPointer();
    size_t size = sendBuf.GetSize();
    if (buf == nullptr || size == 0) {
        ERR("buf is null or size (%zu) is error!", size);
        return SOCKET_SEND_FAIL_RETRY;
    }

    ssize_t ret = SendFully(sendBuf);
    if (ret == SOCKET_SEND_FAIL_DISCONN && GetStatus() != SOCKET_STATUS_DISCONN) {
        ERR("Set TCP status to DISCONN, and send event notice");
        SetStatus(SOCKET_STATUS_DISCONN);
        EventNotice();
    }

    return ret;
}

/*
 * @功能描述：通过TCP接收数据
 * @参数 [in] recvBuf：接收缓存
 * @返回值：大于0表示实际接收到的字节数，小于等于0表示失败
 */
ssize_t VmiTcpSocket::Recv(const VmiBuffer &recvBuf)
{
    // 不是运行态，返回断开错误码
    if (m_status != SOCKET_STATUS_RUNNING) {
        return SOCKET_RECV_FAIL_DISCONN;
    }

    // 检查入参，若非法返回重试错误码
    uint8_t *buf = recvBuf.GetPointer();
    size_t size = recvBuf.GetSize();
    if (buf == nullptr || size == 0) {
        ERR("(TCP) buf is null!");
        return SOCKET_RECV_FAIL_RETRY;
    }

    int socketFd = m_fd;
    unsigned int flag = m_socketOption & SOCKET_OPTION_BITSET_QUICKACK;
    ssize_t ret = ::recv(socketFd, buf, size, 0);
    if (ret <= 0) {
        flag = 0;
        if ((ret == 0) ||
            (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR && errno != ETIMEDOUT)) {
            ERR("(TCP socketFD %d, OBJ %p) disconnected recv fail, ret = %zd. errno (%d):%p, %d",
                socketFd, this, ret, errno, buf, static_cast<int>(size));
            if (GetStatus() != SOCKET_STATUS_DISCONN) {
                SetStatus(SOCKET_STATUS_DISCONN);
                EventNotice();
            }
            ret = SOCKET_RECV_FAIL_DISCONN;
        } else {
            LOG_RATE_LIMIT(ANDROID_LOG_ERROR, 1, "(TCP socketFD %d, OBJ %p) recv fail retry, ret = %zd. errno (%d): %s",
                socketFd, this, ret, errno, strerror(errno));
            ret = SOCKET_RECV_FAIL_RETRY;
        }
    } else {
        UpdateBytesRecv(static_cast<size_t>(ret));
    }

    int optVal[1] = {1};
    if (flag > 0) {
        if (setsockopt(socketFd, IPPROTO_TCP, TCP_QUICKACK, optVal, sizeof(optVal)) < 0) {
            ERR("setsockopt  failed");
        }
    }
    return ret;
}

/*
 * @功能描述：关闭socket
 */
void VmiTcpSocket::CloseSocket()
{
    m_status = SOCKET_STATUS_EXIT;
    SetEventNotice(nullptr);

    if (m_fd != INVALID_FD) {
        int ret = shutdown(m_fd, SHUT_RDWR);
        if (ret < 0) {
            ERR("shutdown failed, ret:%d, errno:%d, fd:%d", ret, errno, m_fd);
        }
        ret = close(m_fd);
        if (ret < 0) {
            ERR("close failed, ret:%d, errno:%d, fd:%d", ret, errno, m_fd);
        }
        m_fd = INVALID_FD;
    }
}
